try:
	from Crypto.Hash import SHA256
	from Crypto.PublicKey import RSA
	from Crypto.Protocol import KDF
	from Crypto.Cipher import AES

	def _sign_rs256(rsakey, input):
		hash = Crypto.Hash.SHA256.new(input).digest()
		sign = rsakey.sign(hash, None)
		return binascii.unhexlify(hex(sign[0])[2:-1])
	def _verify_rs256(rsakey, input, sign):
		hash = Crypto.Hash.SHA256.new(input).digest()
		sign = (int(binascii.hexlify(sign), 16),)
		return rsakey.verify(hash, sign)
	def _get_rsakey(fname, passwd=None):
		with open(fname, 'rb') as file:
			data = file.read()
		return Crypto.PublicKey.RSA.importKey(data, passwd)
	def _encrypt_bytes(data, passwd):
		key = Crypto.Hash.SHA256.new(passwd).digest()
		aes = AES.new(key, AES.MODE_CFB, 'abcdefghijklmnop')
		return aes.encrypt(data)
	def _decrypt_bytes(data, passwd):
		key = Crypto.Hash.SHA256.new(passwd).digest()
		aes = AES.new(key, AES.MODE_CFB, 'abcdefghijklmnop')
		return aes.decrypt(data)



except ImportError:
	Crypto = None
	try:
		import OpenSSL.crypto
	except ImportError:
		OpenSSL = None


from pprint import PrettyPrinter
class MyPrinter(PrettyPrinter):
	def format(self, object, context, maxlevels, level):
		r = repr(object)
		if len(r) > self._width:
			return r[:self._width-4]+'...'+r[-1], False, False
		else:
			return PrettyPrinter.format(self, object, context, maxlevels, level)
pr = MyPrinter(width=60).pprint

from binascii import hexlify, unhexlify
from datetime import datetime, timedelta, tzinfo

def btol(val, nbytes=None):
	if nbytes is None:
		return int(hexlify(val), 16)
	else:
		return int(hexlify(val[-nbytes:]), 16)

def ltob(val, nbytes=None):
	hexval = hex(val)[2:-1] if type(val) is long else hex(val)[2:]
	if nbytes is None:
		return unhexlify('0'*(len(hexval)&1) + hexval)
	else:
		return unhexlify('0'*(nbytes*2-len(hexval)) + hexval[-nbytes*2:])

def	ceildiv(n, d):
	return (n+d-1)//d

def parse_pem(fname):
	with open(fname, 'r') as file:
		data = file.read()
	items = []
	kind = content = None
	for line in data.splitlines():
		if line and line[0] == line[-1] == '-':
			verb, _kind = line.strip('-').split(None, 1)
			if verb == 'BEGIN':
				kind = _kind
				content = []
			elif verb == 'END' and _kind == kind:
				content = b64decode(''.join(content))
				items.append((kind, content))
				kind = content = None
			else:
				kind = content = None
		elif content is not None:
			content.append(line)
	return items

FunctionType = type(lambda:None)

class ASN1:

	BOOLEAN =           1 # bool
	INTEGER =           2 # int or long
	BIT_STRING =        3 # int or long
	OCTET_STRING =      4 # bytes
	NULL =              5 # None
	OBJECT_IDENTIFIER = 6 # OID
#	ObjectDescriptor =  7
#	EXTERNAL =          8
#	REAL =              9 # should be float, but the ASN.1 spec is ?@#$!!!
	ENUMERATED =       10 # int or long
#	EMBEDDED_PDV =     11
	UTF8_STRING =      12 # string with utf-8 codec
	RELATIVE_OID =     13 # OID
#	TIME =             14
	SEQUENCE =         16 # list or tuple
	SET =              17 # set or frozenset
	NUMERIC_STRING =   18 # string with ascii codec
	PRINTABLE_STRING = 19 # string with ascii codec
	TELETEX_STRING =   20 # approximated as string with latin-1 codec
#	VideotexString =   21 # no longer used?
	IA5_STRING =       22 # string with ascii codec
	UTC_TIME =         23 # datetime
	GENERALIZED_TIME = 24 # should be a datetime, but try parsing it
	GRAPHIC_STRING =   25 # approximated as string with latin-1 codec
	VISIBLE_STRING =   26 # string with ascii codec
	GENERAL_STRING =   27 # approximated as string with latin-1 codec
	UNIVERSAL_STRING = 28 # string with utf-32be codec
	BMP_STRING =       30 # string with utf-16be codec

	class NumericString(unicode):
		__slots__ = []
		codec = 'ascii'
		tag = NUMERIC_STRING

	class PrintableString(unicode):
		__slots__ = []
		codec = 'ascii'
		tag = PRINTABLE_STRING

	class VisibleString(unicode):
		__slots__ = []
		codec = 'ascii'
		tag = VISIBLE_STRING

	class IA5String(unicode):
		__slots__ = []
		codec = 'ascii'
		tag = IA5_STRING

	class TeletexString(unicode):
		__slots__ = []
		codec = 'latin-1'
		tag = TELETEX_STRING

	class GraphicString(unicode):
		__slots__ = []
		codec = 'latin-1'
		tag = GRAPHIC_STRING

	class GeneralString(unicode):
		__slots__ = []
		codec = 'latin-1'
		tag = GENERAL_STRING

	class UTF8String(unicode):
		__slots__ = []
		codec = 'utf-8'
		tag = UTF8_STRING

	class BMPString(unicode):
		__slots__ = []
		codec = 'utf-16be'
		tag = BMP_STRING

	class UniversalString(unicode):
		__slots__ = []
		codec = 'utf-32be'
		tag = UNIVERSAL_STRING

	class BitString(long):
		__slots__ = []
		def __repr__(self):
			return bin(self)

	class ObjectID(tuple):
		__slots__ = []
		def __repr__(self):
			return 'OID({})'.format('.'.join([str(n) for n in self]))

	class Sequence(tuple):
		__slots__ = []
		def __getitem__(self, idx):
			try:
				return tuple.__getitem__(self, idx)
			except IndexError:
				return None
		def __getslice__(self, idx, jdx):
			length = len(self)
			if jdx > length:
				return tuple.__getslice__(self + (None,)*(jdx - length), idx, jdx)
			else:
				return tuple.__getslice__(self, idx, jdx)
		def get_tag(self, tag, default=None):
			next([item for item in self if getattr(item, 'tag', None) == tag], default)

	class TaggedValue(object):
		__slots__ = ['tag', 'value']
		def __repr__(self):
			if isinstance(self.value, FunctionType):
				return '{}:<{!r}>'.format(self.tag, self.value(ASN1.OCTET_STRING))
			else:
				return '{}:{!r}'.format(self.tag, self.value)

	class TimeZone(tzinfo):
		ZERO = timedelta(0)
		def __init__(self, tzname, tzoffset):
			self._name = tzname
			self._offset = timedelta(minutes=tzoffset)
		def __repr__(self):
			return repr(self._name)
		def utcoffset(self, dt):
			return self._offset
		def dst(self, dt):
			return TimeZone.ZERO
		def tzname(self, dt):
			return self._name

#	@staticmethod
#	def unpack(seq, count, default=None):
#		if count > len(seq):
#			return seq + [default]*(count-len(seq))
#		else:
#			return seq[:count]

	@staticmethod
	def loads(data):
		item, offset = ASN1._decode_item(data, 0)
		if offset != len(data):
			raise ValueError('invalid DER structure')
		return item

	@staticmethod
	def dumps(item):
		data = []
		ASN1._encode_item(item, data)
		return b''.join(data)

	@staticmethod
	def _encode_item(item, data):
		if isinstance(item, ASN1.TaggedValue):
			if isinstance(item.value, FunctionType):
				pass
			else:
				tag, length = ASN1._encode_item(item.value, data)
		elif hasattr(item, '__iter__'):
			# Note: this test is sufficient (in Python 2.7 at least) to
			#       distinguish between string types and other sequences.
			tag, length = ASN1._encode_constructed(item, data)
		else:
			tag, length = ASN1._encode_primitive(item, data)

	@staticmethod
	def _decode_item(data, offset):
		byte = ord(data[offset])
		offset += 1
		tag = byte & 0x1F
		con = byte & 0x20
		cls = byte >> 6
		if tag == 0x1F:
			raise ValueError('unsupported DER item type')
		length = ord(data[offset])
		offset += 1
		if length & 0x80:
			endidx = offset + (length & 0x7F)
			length = btol(data[offset:endidx])
			offset = endidx
		endidx = offset + length
		if endidx > len(data):
			raise ValueError('invalid DER item length')
		value = data[offset:endidx]
		if cls:
			item = ASN1.TaggedValue()
			item.tag = (cls, tag)
			if con: # explicitly tagged type
				value, offset = ASN1._decode_item(data, offset)
				if offset != endidx:
					raise ValueError('invalid DER tagged type')
				item.value = value
			else: # implicitly tagged type
				item.value = lambda kind: ASN1._decode_primitive(kind, value)
		elif con: # constructed type
			item = ASN1._decode_constructed(tag, value)
		else: # primitive type
			item = ASN1._decode_primitive(tag, value)
		return item, endidx

	@staticmethod
	def _decode_constructed(tag, value):
		if tag in (ASN1.SEQUENCE, ASN1.SET):
			seq = []
			offset = 0
			endidx = len(value)
			while offset < endidx:
				item, offset = ASN1._decode_item(value, offset)
				seq.append(item)
			if offset != endidx:
				raise ValueError('invalid DER sequence')
			if tag == ASN1.SEQUENCE:
				item = tuple(seq)
			else:
				item = frozenset(seq)
		else:
			raise ValueError('invalid DER constructed type: {}'.format(tag))
		return item

	@staticmethod
	def _encode_primitive(item, data):
		if isinstance(item, bool):
			tag = ASN1.BOOLEAN
			value = b'\xFF' if item else b'\0'
		elif isinstance(item, ASN1.BitString):
			tag = ASN1.BIT_STRING
			value = b'\0' + ltob(item)
		elif isinstance(item, (int, long)):
			tag = ASN1.INTEGER
			if item < 0:
				item += 256**((item.bit_length()+8)//8)
			value = ltob(item)
		elif isinstance(item, bytes):
			tag = ASN1.OCTET_STRING
			value = item
		elif item is None:
			tag = ASN1.NULL
			value = b''
		elif isinstance(item, ASN1.ObjectID):
			if len(item) < 2:
				raise ValueError('invalid ASN1.ObjectID')
			if item[0] is None:
				tag = ASN1.RELATIVE_OID
				item = item[1:]
			else:
				tag = ASN1.OBJECT_IDENTIFIER
				item = (item[0]*40 + item[1],) + item[2:]
			value = []
			for valn in item:
				valn, byte = divmod(valn, 128)
				part = chr(byte)
				while valn:
					valn, byte = divmod(valn, 128)
					part = chr(byte | 0x80) + part
				value.append(part)
			value = b''.join(value)
		elif isinstance(item, unicode):
			try:
				tag = item.tag
				value = item.encode(item.codec)
			except AttributeError:
				largest = ord(max(item))
				if largest < 128:
					tag = IA5_STRING
					value = item.encode(IA5String.codec)
				else:
					tag = UTF8_STRING
					value = item.encode(UTF8String.codec)
		elif isinstance(item, datetime):
			tag = UTC_TIME
			if item.utcoffset(): # tzinfo present and offset not 0
				format = '%y%m%d%H%M%S%z'
			else:
				format = '%y%m%d%H%M%SZ'
			value = item.strftime(format)
		else:
			raise TypeError('invalid type for DER encoding')



	@staticmethod
	def _decode_primitive(tag, value):
		if tag == ASN1.BOOLEAN:
			return value != b'\0'
		elif tag in (ASN1.INTEGER, ASN1.ENUMERATED):
			length = len(value)
			item = btol(value)
			if item >> (length * 8 - 1):
				item -= 256**length
			return item
		elif tag == ASN1.BIT_STRING:
			return ASN1.BitString(btol(value[1:]))
		elif tag == ASN1.OCTET_STRING:
			return bytes(value)
		elif tag == ASN1.NULL:
			return None
		elif tag in (ASN1.OBJECT_IDENTIFIER, ASN1.RELATIVE_OID):
			oid = []
			valn = 0
			for char in value:
				byte = ord(char)
				valn = (valn * 128) + (byte & 0x7F)
				if byte & 0x80 == 0:
					oid.append(valn)
					valn = 0
			if valn != 0:
				raise ValueError('invalid DER object identifier')
			if tag == ASN1.OBJECT_IDENTIFIER:
				return ASN1.ObjectID(divmod(oid[0], 40) + tuple(oid[1:]))
			else:
				return ASN1.ObjectID([None] + oid)
		elif tag == ASN1.NUMERIC_STRING:
			return ASN1.NumericString(value, ASN1.NumericString.codec)
		elif tag == ASN1.PRINTABLE_STRING:
			return ASN1.PrintableString(value, ASN1.PrintableString.codec)
		elif tag == ASN1.VISIBLE_STRING:
			return ASN1.VisibleString(value, ASN1.VisibleString.codec)
		elif tag == ASN1.IA5_STRING:
			return ASN1.IA5String(value, ASN1.IA5String.codec)
		elif tag == ASN1.TELETEX_STRING:
			return ASN1.TeletexString(value, ASN1.TeletexString.codec)
		elif tag == ASN1.GRAPHIC_STRING:
			return ASN1.GraphicString(value, ASN1.GraphicString.codec)
		elif tag == ASN1.GENERAL_STRING:
			return ASN1.GeneralString(value, ASN1.GeneralString.codec)
		elif tag == ASN1.UTF8_STRING:
			return ASN1.UTF8String(value, ASN1.UTF8String.codec)
		elif tag == ASN1.BMP_STRING:
			return ASN1.BMPString(value, ASN1.BMPString.codec)
		elif tag == ASN1.UNIVERSAL_STRING:
			return ASN1.UniversalString(value, ASN1.UniversalString.codec)
		elif tag == ASN1.UTC_TIME:
			split = -1 if value[-1] == 'Z' else -5
			dt, tz = value[:split], value[split:]
			tzm = int(tz[0]+'1')*(int(tz[1:3])*60 + int(tz[3:5])) if tz != 'Z' else 0
			format = '%y%m%d%H%M%S' if len(dt) == 12 else '%y%m%d%H%M'
			return datetime.strptime(dt, format).replace(tzinfo=ASN1.TimeZone(tz, tzm))
		elif tag == ASN1.GENERALIZED_TIME:
			return value.decode('ascii')
		else:
			raise ValueError('invalid DER primitive type: {}'.format(tag))


OID_RSADSI = (1, 2, 840, 113549)
OID_X957 = (1, 2, 840, 10040)
OID_NIST = (2, 16, 840, 1, 101, 3, 4)

OID_PKCS = OID_RSADSI + (1,)
OID_PKCS1 = OID_PKCS + (1,)
OID_PKCS5 = OID_PKCS + (5,)
OID_PKCS7 = OID_PKCS + (7,)
OID_PKCS9 = OID_PKCS + (9,)
OID_PKCS12 = OID_PKCS + (12,)

OID_PKCS1_RSAENCRYPTION = OID_PKCS1 + (1,)
OID_X957_DSA = OID_X957 + (4, 1)
#OID_X957_DSA_SHA1 = OID_X957 + (4, 3)

#OID_PKCS1_MD2_RSAENCRYPTION = OID_PKCS1 + (2,)
#OID_PKCS1_MD5_RSAENCRYPTION = OID_PKCS1 + (4,)
#OID_PKCS1_SHA1_RSAENCRYPTION = OID_PKCS1 + (5,)
#OID_PKCS1_SHA256_RSAENCRYPTION = OID_PKCS1 + (11,)
#OID_PKCS1_SHA384_RSAENCRYPTION = OID_PKCS1 + (12,)
#OID_PKCS1_SHA512_RSAENCRYPTION = OID_PKCS1 + (13,)
#OID_PKCS1_ID_RSAES_OAEP = OID_PKCS1 + (7,)
#OID_PKCS1_ID_RSASSA_PSS = OID_PKCS1 + (10,)

OID_PCKS7_DATA = OID_PKCS7 + (1,)
OID_PCKS7_SIGNED_DATA = OID_PKCS7 + (2,)
OID_PCKS7_ENVELOPED_DATA = OID_PKCS7 + (3,)
OID_PCKS7_SIGNED_AND_ENVELOPED_DATA = OID_PKCS7 + (4,)
OID_PCKS7_DIGESTED_DATA = OID_PKCS7 + (5,)
OID_PCKS7_ENCRYPTED_DATA = OID_PKCS7 + (6,)

OID_PKCS9_X590CERT = OID_PKCS9 + (22, 1)
OID_PKCS9_SDSICERT = OID_PKCS9 + (22, 2)

OID_PCKS12_KEYBAG = OID_PKCS12 + (10, 1, 1)
OID_PCKS12_SHROUDED_KEYBAG = OID_PKCS12 + (10, 1, 2)
OID_PCKS12_CERTBAG = OID_PKCS12 + (10, 1, 3)
OID_PCKS12_CRLBAG = OID_PKCS12 + (10, 1, 4)
OID_PCKS12_SECRETBAG = OID_PKCS12 + (10, 1, 5)
OID_PCKS12_SAFECONTENTS = OID_PKCS12 + (10, 1, 6)

OID_PKCS5_PBKDF2 = OID_PKCS5 + (12,)

PKCS_PBES_PARAMS = {OID_PKCS12 + (1, 1): ('PBES3', 'SHA', 128,  'ARC4',  None), # pbeWithSHAAnd128BitRC4
					OID_PKCS12 + (1, 2): ('PBES3', 'SHA', 40,   'ARC4',  None), # pbeWithSHAAnd40BitRC4
					OID_PKCS12 + (1, 3): ('PBES3', 'SHA', 192,  'DES3', 'CBC'), # pbeWithSHAAnd3-KeyTripleDES-CBC
					OID_PKCS12 + (1, 4): ('PBES3', 'SHA', 128,  'DES3', 'CBC'), # pbeWithSHAAnd2-KeyTripleDES-CBC
					OID_PKCS12 + (1, 5): ('PBES3', 'SHA', 128,  'ARC2', 'CBC'), # pbeWithSHAAnd128BitRC2-CBC
					OID_PKCS12 + (1, 6): ('PBES3', 'SHA', 40,   'ARC2', 'CBC'), # pbewithSHAAnd40BitRC2-CBC
					OID_PKCS5 + (1,):    ('PBES1', 'MD2', 64,   'DES',  'CBC'), # pbeWithMD2AndDES-CBC
					OID_PKCS5 + (3,):    ('PBES1', 'MD5', 64,   'DES',  'CBC'), # pbeWithMD5AndDES-CBC
					OID_PKCS5 + (4,):    ('PBES1', 'MD2', 64,   'ARC2', 'CBC'), # pbeWithMD2AndRC2-CBC
					OID_PKCS5 + (6,):    ('PBES1', 'MD5', 64,   'ARC2', 'CBC'), # pbeWithMD5AndRC2-CBC
					OID_PKCS5 + (9,):    ('PBES1', 'MD5', 64,   'XOR',   None), # pbeWithMD5AndXOR
					OID_PKCS5 + (10,):   ('PBES1', 'SHA', 64,   'DES',  'CBC'), # pbeWithSHA1AndDES-CBC
					OID_PKCS5 + (11,):   ('PBES1', 'SHA', 64,   'ARC2', 'CBC'), # pbeWithSHA1AndRC2-CBC
					OID_PKCS5 + (13,):   ('PBES2', None,  None,  None,   None)}

PKCS_DIGEST_PARAMS = {OID_RSADSI + (2, 7):  ('HMAC', 'SHA'),    # hmacWithSHA1
					  OID_RSADSI + (2, 8):  ('HMAC', 'SHA224'), # hmacWithSHA224
					  OID_RSADSI + (2, 9):  ('HMAC', 'SHA256'), # hmacWithSHA256
					  OID_RSADSI + (2, 10): ('HMAC', 'SHA384'), # hmacWithSHA384
					  OID_RSADSI + (2, 11): ('HMAC', 'SHA512')} # hmacWithSHA512

PKCS_CIPHER_PARAMS = {(1, 3, 14, 3, 2, 7): (64,   'DES',  'CBC'), # DES-CBC-Pad
					  OID_RSADSI + (3, 7): (192,  'DES3', 'CBC'), # DES-EDE3-CBC-Pad
					  OID_RSADSI + (3, 2): (None, 'ARC2', 'CBC'), # RC2-CBC-Pad
					  OID_RSADSI + (3, 3): (None, 'ARC2', 'ECB'),
#					  OID_RSADSI + (3, 4): (None, 'ARC4',  None),
#					  OID_RSADSI + (3, 8): (None, 'ARC5',  None), # RC5-CBC
#					  OID_RSADSI + (3, 9): (None, 'ARC5',  None), # RC5-CBC-Pad
					  OID_NIST + (1, 1):   (128,  'AES',  'ECB'), # AES128-ECB-Pad
					  OID_NIST + (1, 2):   (128,  'AES',  'CBC'), # AES128-CBC-Pad
					  OID_NIST + (1, 3):   (128,  'AES',  'OFB'), # AES128-OFB-Pad
					  OID_NIST + (1, 4):   (128,  'AES',  'CFB'), # AES128-CFB-Pad
					  OID_NIST + (1, 21):  (192,  'AES',  'ECB'), # AES192-ECB-Pad
					  OID_NIST + (1, 22):  (192,  'AES',  'CBC'), # AES192-CBC-Pad
					  OID_NIST + (1, 23):  (192,  'AES',  'OFB'), # AES192-OFB-Pad
					  OID_NIST + (1, 24):  (192,  'AES',  'CFB'), # AES192-CFB-Pad
					  OID_NIST + (1, 41):  (256,  'AES',  'ECB'), # AES256-ECB-Pad
					  OID_NIST + (1, 42):  (256,  'AES',  'CBC'), # AES256-CBC-Pad
					  OID_NIST + (1, 43):  (256,  'AES',  'OFB'), # AES256-OFB-Pad
					  OID_NIST + (1, 44):  (256,  'AES',  'CFB')} # AES256-CFB-Pad

OID_ID_AT = (2, 5, 4)
PKCS_ATTRNAMES = {OID_PKCS9 + (1,):  'emailAddress',
				  OID_PKCS9 + (20,): 'friendlyName',
				  OID_PKCS9 + (21,): 'localKeyId',
				  OID_ID_AT + (3,):  'cn', # commonName
				  OID_ID_AT + (4,):  'sn', # surname
				  OID_ID_AT + (6,):  'c',  # countryName
				  OID_ID_AT + (10,): 'o',  # organizationName
				  OID_ID_AT + (11,): 'ou', # organizationalUnitName
				  OID_ID_AT + (41,): 'name',
				  OID_ID_AT + (42,): 'givenName'}

def pkcs_makekey(algo, data, attrs, public):
	algoId, algoParams = ASN1.unpack(algo, 2)
	if algoId == OID_PKCS1_RSA:
		from Crypto.PublicKey import RSA
		if public:
			vals = ASN1.loads(ltob(data))[0:2] # n, e
		else:
			vals = ASN1.loads(data)[1:6] # n, e, d, p, q
		key = RSA.construct([long(val) for val in vals])
	elif algoId == OID_X957_DSA:
		from Crypto.PublicKey import DSA
		p, q, g = algoParams
		if public:
			y = ASN1.loads(ltob(data))
			vals = (y, g, p, q)
		else:
			x = ASN1.loads(data)
			y = pow(g, x, p)
			vals = (y, g, p, q, x)
		key = DSA.construct([long(val) for val in vals])
	else:
		raise ValueError('unknown key algorithm id: {}'.format(algoId))
	if attrs:
		for attrId, attrValues in attrs:
			if attrId in PKCS_ATTRNAMES and attrValues:
				setattr(key, PKCS_ATTRNAMES[attrId], attrValues[0])
	return key

def pkcs_getcertkey(certId, certValue, bagAttrs):
	if certId == OID_PKCS_X590CERT:
		cert = ASN1.loads(certValue.value)
		cert = cert[0]
		algo, data = cert[6]
		key = pkcs_makekey(algo, data, bagAttrs, True)
		key.serialNumber = cert[1]
		key.validFrom = cert[4][0]
		key.validTo = cert[4][1]
		key.issuer = tuple(['{}={}'.format(PKCS_ATTRNAMES.get(id, id), val) for id, val in cert[3][0]])
		key.subject = tuple(['{}={}'.format(PKCS_ATTRNAMES.get(id, id), val) for id, val in cert[5][0]])
	elif certId == OID_PKCS9_SDSICERT:
		cert = b64decode(certValue.value)
		print "========  SDSI CERT  =============="
		pr(cert)
		print "==================================="
	return key

def pkcs_pbesencrypt(algoInfo, data, passwd):
	cipher = pkcs_pbescipher(algoInfo, passwd)
	if cipher.block_size > 1:
		pad = cipher.block_size - length(data) % cipher.block_size
		data = data + chr(pad)*pad
		assert len(data) % cipher.block_size == 0
	return cipher.encrypt(data)

def pkcs_pbesdecrypt(algoInfo, data, passwd):
	cipher = pkcs_pbescipher(algoInfo, passwd)
	data = cipher.decrypt(data)
	if cipher.block_size > 1:
		pad = ord(data[-1])
		if not (1 <= pad <= cipher.block_size and data[-pad:] == data[-1]*pad):
			raise ValueError('error decrypting data')
		data = data[:-pad]
	return data

def pkcs_pbescipher(algoInfo, passwd):
	algoId, algoParms = ASN1.unpack(algoInfo, 2)
	pbes, hash, nbits, cipher, mode = PKCS_PBES_PARAMS[algoId]
	print pbes, hash, nbits, cipher, mode
	dklen = nbits//8
	key = init = None
	hash = getattr(__import__('Crypto.Hash', fromlist=[hash]), hash)
	cipher = getattr(__import__('Crypto.Cipher', fromlist=[cipher]), cipher)
	if pbes == 'PBES1':
		salt, count = algoParms
		dk = pkcs_pbeskdf1(passwd, salt, count, 16, hash)
		key, init = dk[:8], dk[8:]
	elif pbes == 'PBES2':
		kdfAlgo, ciphAlgo = algoParms
		kdfId, kdfParms = ASN1.unpack(kdfAlgo, 2)
		if kdfId == OID_PKCS5_PBKDF2:
			salt, count, dklen, prf = ASN1.unpack(kfdParms, 4)
#			prf = prf or OID_RSADSI + (2, 7) # id-hmacWithSHA1
			hmac, hash = PKCS_DIGEST_PARAMS.get(prf, ('HMAC', 'SHA'))
		key = pkcs_pbeskdf2(passwd, salt, count, dklen, hash)

		ciphId, ciphParms = ASN1.unpack(ciphAlgo, 2)
		nbits, cipher, mode = PKCS_CIPHER_PARAMS[ciphId]
		if cipher == 'ARC2':
			nbits, init = ciphParms
			nbits = {160:40, 120:64, 58:128}.get(nbits, nbits)
		else: # DES, DES3, AES
			init = ciphParms

	elif pbes == 'PBES3':
		salt, count = algoParms
		passwd = (unicode(passwd)+u'\0').encode('utf-16be')
		key = pkcs_pbeskdf3(passwd, salt, count, dklen, hash, 1)
		if cipher.block_size > 1:
			init = pkcs_pbeskdf3(passwd, salt, count, cipher.block_size, hash, 2)
	kwargs = {}
	if init:
		kwargs['IV'] = init
	if mode:
		kwargs['mode'] = getattr(cipher, 'MODE_' + mode)
	if cipher.__name__ == 'Crypto.Cipher.ARC2':
		kwargs['effective_keylen'] = nbits  # this one little line took me two days to figure out
	print hexlify(key), kwargs
	return cipher.new(key, **kwargs)

def pkcs_pbeskdf1(passwd, salt, count, dklen, hash):
	P = passwd
	S = salt
	T = reduce(lambda Ti,_: hash.new(Ti).digest(), xrange(count), P+S)
	return T[:dklen]

def pkcs_pbeskdf2(passwd, salt, count, dklen, hash):
	u = hash.digest_size
	l = ceildiv(dklen,u)
	P = passwd
	S = salt
	def U(Ux, Ui):
		Ui = HMAC.new(P, Ui, hash).digest()
		Ux = strxor(Ux, Ui)
		return Ux, Ui
	Uo = ltob(0, u)
	T = [reduce(lambda (Ux,Ui),_:
				U(Ux,Ui), xrange(count), (Uo, S+ltob(i,4)))
			for i in xrange(l)]
	return ''.join(T)[:dklen]

def pkcs_pbeskdf3(passwd, salt, count, dklen, hash, id):
	u = hash.digest_size
	v = hash.block_size
	def makelen(bytes, tolen):
		q, r = divmod(tolen, len(bytes)) if bytes else (0, 0)
		return bytes*q + bytes[:r]
	D = chr(id)*v
	S = makelen(salt, v*ceildiv(len(salt),v))
	P = makelen(passwd, v*ceildiv(len(passwd),v))
	I = S+P
	c = ceildiv(dklen,u)
	A = []
	for i in xrange(1, c+1):
		Ai = reduce(lambda Ai,_: hash.new(Ai).digest(), xrange(count), D+I)
		A.append(Ai)
		if i == c: break
		B = btol(makelen(Ai, v)) + 1
		I = ''.join([ltob(btol(I[j:j+v]) + B, v) for j in range(0, len(I), v)])
#		Ip = b''
#		for j in range(0, len(I), v):
#			Ij = I[j:j+v]
#			assert len(Ij) == v
#			Ij = btol(Ij) + B
##			Ij = int(hexlify(Ij), 16) + B + 1 #  % 2**v
#			print 'Ij', Ij.bit_length(), hex(Ij)
#			Ij = ltob(Ij, v)
#			assert len(Ij) == v
#			Ip += Ij
#		I = Ip
	# parity bits should be set for DES and 3DES keys, but
	# the DES implementation in PyCrypto just ignores them.
	return ''.join(A)[:dklen]

def pkcs12_iteritems(data, passwd=None, kind='*'):
	pkcsobj = ASN1.loads(data)
	version, authSafe, macData = ASN1.unpack(pkcsobj, 3)
	if version != 3:
		raise ValueError('invalid pkcs12 version: {}'.format(version))
	safeType, safeContent = authSafe
	if safeType == OID_PCKS7_DATA:
		authSafe = ASN1.loads(safeContent.value)
	elif safeType == OID_PCKS7_SIGNED_DATA:
		authSafe = XXX # fixme
	for contType, content in authSafe:
		if contType == OID_PCKS7_DATA:
			safeContents = ASN1.loads(content.value)
		elif contType == OID_PCKS7_ENCRYPTED_DATA:
			version, encrInfo = ASN1.unpack(content.value, 2)
			contType, encrAlgo, encrData = ASN1.unpack(encrInfo, 3)
			if encrData:
				encrData = encrData.value(ASN1.OCTET_STRING)
				contData = pkcs_pbesdecrypt(encrAlgo, encrData, passwd)
				safeContents = ASN1.loads(contData)
#		elif contType == OID_PCKS7_ENVELOPED_DATA:
#			print 'found an ENVELOPED DATA content'
#			continue
		else:
			print 'found a', contType
			continue
		for safeBag in safeContents:
			bagId, bagValue, bagAttrs = ASN1.unpack(safeBag, 3)
			if bagId == OID_PCKS12_KEYBAG: # and foo==PRIVKEY:
				version, algo, data = ASN1.unpack(bagValue.value, 3)
				yield pkcs_makekey(algo, data, bagAttrs, False)
			elif bagId == OID_PCKS12_SHROUDED_KEYBAG: # and foo==PRIVKEY:
				encrAlgo, encrData = bagValue.value
				pkeyData = pkcs_pbesdecrypt(encrAlgo, encrData, passwd)
				pkeyInfo = ASN1.loads(pkeyData)
				version, algo, data = ASN1.unpack(pkeyInfo, 3)
				yield pkcs_makekey(algo, data, bagAttrs, False)
			elif bagId == OID_PCKS12_CERTBAG: # and foo==CERT:
				certId, certValue = bagValue.value
				yield pkcs_getcertkey(certId, certValue, bagAttrs)
			else:
				print 'found a', bagId, 'bag', bagAttrs
		
	
	